package ngat.oss.simulation;

import java.io.*;
import java.text.*;
import java.util.*;

import ngat.oss.simulation.EnvironmentalScenario.SeeingProfileEntry;
import ngat.phase2.Group;
import ngat.util.ConfigurationProperties;
import ngat.util.GaussianRandom;


/** Environmental scenario generator. Generates environmental scenarios according to supplied control parameters.*/
public class EnvironmentalScenarioGenerator implements EnvironmentalScenarioProvider  {

    public static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm");
    public static final SimpleTimeZone UTC   = new SimpleTimeZone(0, "UTC");

    static {
	sdf.setTimeZone(UTC);
    }

    /** The scenarion provided by the EnvironmentalScenarioGenerator.*/
    EnvironmentalScenario scenario;

    /** Create an EnvironmentalScenarioGenerator.*/
    public EnvironmentalScenarioGenerator() {

    }

    /** Setup scenarion generator params from the supplied configuration.
     * @param config A set of configuration properties to define the scenario.
     * @return Pre-configured EnvironmentalScenarioGeneratorParams.
     */
    public EnvironmentalScenarioGeneratorParams createScenarioParams(ConfigurationProperties config) throws Exception {

	EnvironmentalScenarioGeneratorParams p = new EnvironmentalScenarioGeneratorParams();

	// ID
	p.name    = config.getProperty("scenario.name");

	// scenario limits
	p.start   = (config.getDateValue("start", "yyyy-MM-dd HH:mm")).getTime();
	p.end     = (config.getDateValue("end",   "yyyy-MM-dd HH:mm")).getTime();

	// EX seeing
	p.meanEx  = config.getDoubleValue("mean.ex");
	p.sigmaEx = config.getDoubleValue("sigma.ex");
	p.minEx   = config.getDoubleValue("min.ex");
	
	// AV seeing
	p.meanAv  = config.getDoubleValue("mean.av");
	p.sigmaAv = config.getDoubleValue("sigma.av");
	p.minAv   = config.getDoubleValue("min.av");

	// POOR seeing
	p.meanPoor  = config.getDoubleValue("mean.poor");
	p.sigmaPoor = config.getDoubleValue("sigma.poor");
	p.minPoor   = config.getDoubleValue("min.poor");

	// CRAP seeing
	p.meanCrap  = config.getDoubleValue("mean.crap");
	p.sigmaCrap = config.getDoubleValue("sigma.crap");
	p.minCrap   = config.getDoubleValue("min.crap");

	// Extinction (classified as photom OR spectro)
	p.photomFraction = config.getDoubleValue("photom.fraction");

	return p;
    }
	
    

    /** Create an EnvironmentalScenario from supplied model parameters.
     * @param params The EnvironmentalScenarioGeneratorParams which define the model.
     */
    public EnvironmentalScenario createEnvironmentalScenario(EnvironmentalScenarioGeneratorParams params) throws Exception {

	// Create random generators for the various seeing condition windows.
	GaussianRandom gex   = new GaussianRandom(params.meanEx,   params.sigmaEx,   params.minEx);
	GaussianRandom gav   = new GaussianRandom(params.meanAv,   params.sigmaAv,   params.minAv);
	GaussianRandom gpoor = new GaussianRandom(params.meanPoor, params.sigmaPoor, params.minPoor);
	GaussianRandom gcrap = new GaussianRandom(params.meanCrap, params.sigmaCrap, params.minCrap);

	// work out overall probs.
	double norm = params.meanEx + params.meanAv + params.meanPoor +params.meanCrap;
	double pex  = params.meanEx/norm;
	double pav  = params.meanAv/norm;
	double ppo  = params.meanPoor/norm;

	scenario = new EnvironmentalScenario(params.name, pex, pav, ppo, params.photomFraction);

	long startTime = params.start;
	long endTime = params.end;
	
	// select the start-of-night seeing randomly.
	int currsee = scenario.randomSeeing();
	
	System.err.println("Starting "+Group.toSeeingString(currsee)+" at "+ScheduleSimulator.sdf.format(new Date(params.start)));
	
	// we are this far into the initial seeing period 
	double rmid = Math.random(); 
	EnvironmentSnapshot env = new EnvironmentSnapshot();
	env.seeing = currsee;
	env.photom = scenario.randomPhotom();

	long s1     = 0L;
	switch (currsee) {
	case Group.EXCELLENT:
	    s1  = (long)(3600000.0*gex.random());         // length of initial window
	    break;
	case Group.AVERAGE:
	    s1 = (long)(3600000.0*gav.random());          // length of initial window
	    break;
	case Group.POOR:
	    s1 = (long)(3600000.0*gpoor.random());        // length of initial window
	    break;
	case Group.CRAP: 
	    s1 = (long)(3600000.0*gcrap.random());        // length of initial window
	    break;
	}
	long s1pre  = params.start - (long)(rmid*(double)s1);       // when did it start.
	long s1post = params.start + (long)((1.0-rmid)*(double)s1); // when does it end.
	startTime = s1pre;
	scenario.addScenarioEntry(new SeeingProfileEntry(s1pre, s1post, env));

	long t = s1post;
	while (t < params.end) {
	    
	    // cannot do the same as last one...
	    int nextsee = scenario.randomSeeing();
	    while (nextsee == currsee)
		nextsee = scenario.randomSeeing();
	    
	    EnvironmentSnapshot env1 = new EnvironmentSnapshot();
	    env1.seeing = nextsee;
	    env1.photom = scenario.randomPhotom();

	    long lsee = 0L;
	    
	    switch (nextsee) {
		
	    case Group.EXCELLENT:
		lsee = (long)(3600000.0*gex.random());
		break;
	    case Group.AVERAGE:
		lsee = (long)(3600000.0*gav.random());
		break;
	    case Group.POOR:
		lsee = (long)(3600000.0*gpoor.random());
		break;
	    case Group.CRAP:
		lsee = (long)(3600000.0*gcrap.random());
		break;
	    }
	   
	    scenario.addScenarioEntry(new SeeingProfileEntry(t, t+lsee, env1));
	    t += lsee;
	    System.err.println(Group.toSeeingString(nextsee)+" window till: "+ScheduleSimulator.sdf.format(new Date(t)));
	    currsee = nextsee;
	}	    
	
	endTime = t;
	
	System.err.println("Ending  at "+ScheduleSimulator.sdf.format(new Date(endTime)));
	
	scenario.setStartTime(startTime);
	scenario.setEndTime(endTime);
				  return scenario;
    }

    /** Load a scenario from file.*/
    public static EnvironmentalScenario loadScenario(File file) throws Exception {

	ConfigurationProperties config = new ConfigurationProperties();
	config.load(new FileInputStream(file));
	
	EnvironmentalScenario scenario = new EnvironmentalScenario();
	scenario.setScenarioName(config.getProperty("scenario.name"));

	// use these to deduce probability factors
	double pex = 0.0;
	double pav = 0.0;
	double ppo = 0.0;
	double pcr = 0.0;
	double fpho=0.0;
	double npho=0.0;

	long ats = System.currentTimeMillis() + 100*365*24*86400*1000L; // plus 100 years
	long ate = System.currentTimeMillis() - 100*365*24*86400*1000L; // back 100 years
	Enumeration e = config.propertyNames();
	while (e.hasMoreElements()) {

	    String key = (String)e.nextElement();

	    // skip non window items...
	    if (! key.trim().startsWith("window"))
		continue;

	    String line = (String)config.getProperty(key);

	    // Format:  [yyyy-MM-dd] [HH:mm] [yyyy-MM-dd] [HH:mm] [sval] ) 5 params
	  
	    StringTokenizer tt = new StringTokenizer(line);
	    if (tt.countTokens() != 6)
		throw new IllegalArgumentException("Missing tokens on line, expected 6 found "+tt.countTokens());

	    String ds = tt.nextToken()+" "+tt.nextToken();
	    long ts = (sdf.parse(ds)).getTime();

	    String de = tt.nextToken()+" "+tt.nextToken();
	    long te = (sdf.parse(de)).getTime();

	    String ss = tt.nextToken();
	    int seeing = Integer.parseInt(ss);
	
	    String sp = tt.nextToken();
	    boolean photom = (sp.equals("photom"));

	    EnvironmentSnapshot env1 = new EnvironmentSnapshot();
	    env1.seeing = seeing;
	    env1.photom = photom;
	    scenario.addScenarioEntry(new SeeingProfileEntry(ts,te,env1));	    

	    switch (seeing) {
	    case Group.EXCELLENT:
		pex += (double)(te-ts);
		break;
	    case Group.AVERAGE:
		pav += (double)(te-ts);
		break;
	    case Group.POOR:
		ppo += (double)(te-ts);
		break;
	    default:
		pcr += (double)(te-ts);
	    }

	    if (photom)
		fpho += (double)(te-ts);
	    else
		npho += (double)(te-ts);

	    if (ts < ats)
		ats = ts;
	    if (te > ate)
		ate = te;

	}

	double norm = pex + pav + ppo + pcr;
	scenario.setProbabilityExcellent(pex/norm);
	scenario.setProbabilityAverage(pav/norm);
	scenario.setProbabilityPoor(ppo/norm);
	scenario.setProbabilityPhotom(fpho/(fpho+npho));

	scenario.setStartTime(ats);
	scenario.setEndTime(ate);
	return scenario;

    }

    /** Dump the scenario to a file.*/
    public static void dumpScenario(EnvironmentalScenario scenario, File file)  throws Exception {

	PrintStream pout = new PrintStream(new FileOutputStream(file));

	pout.println("scenario.name = "+scenario.getScenarioName());

	int ii = 0;
	Iterator it = scenario.listScenarioEntries();
	while (it.hasNext()) {
	    ii++;
	    SeeingProfileEntry spe = (SeeingProfileEntry)it.next();
	    pout.println("window."+ii+" = "+
			 sdf.format(new Date(spe.stime))+" "+
			 sdf.format(new Date(spe.etime))+" "+
			 spe.env.seeing+" "+
			 (spe.env.photom ? "photom" : "spectro"));
	}	
	
	pout.close();
    }

    /** Returns an EnvironmentalScenario.*/
    public EnvironmentalScenario getScenario() {
	return scenario;
    }

}
